package com.example.controllers;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

import com.example.ShoppingCartApiApplication;
import com.example.dtos.UsersDTO;
import com.example.exceptions.UserAlreadyExistsException;
import com.example.models.Users;
import com.example.repositories.UsersRepository;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ShoppingCartApiApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=9090"})
public class UsersControllerIT {
	private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));

    private MockMvc mockMvc;
    
    private HttpMessageConverter mappingJackson2HttpMessageConverter;
    
    private Users user1, user2,user3;
    private UsersDTO userDTO1,userDTO2,userDTO3;
    
    @Autowired
    private WebApplicationContext webApplicationContext;
    
    @Autowired
    private UsersRepository usersRepository;
    
    @Autowired
    void setConverters(HttpMessageConverter<?>[] converters) {

        this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
                hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();

        Assert.assertNotNull("the JSON message converter must not be null",
                this.mappingJackson2HttpMessageConverter);
    }
    
    @Before
    public void setup() throws Exception {
        this.mockMvc = webAppContextSetup(webApplicationContext).build();
        
        this.usersRepository.deleteAll();
        
        this.user1 = usersRepository.save(new Users("ilancho", "ilancho", true));
        this.user2 = usersRepository.save(new Users("juancho", "abcdefg", true));
        this.user3= usersRepository.save(new Users("benjamin", "password3", true));
        this.userDTO1 = new UsersDTO("ilancho", "ilancho", true);
        this.userDTO2 = new UsersDTO("juancho", "abcdefg", true);
        this.userDTO3 = new UsersDTO("benjamin", "password3", true);
    }
    
//    @Test
//    public void login() throws Exception {
//    	mockMvc.perform(post("/users/login")
//	 			.contentType(contentType)
//	 			.content(json(userDTO1)))
//                .andExpect(status().isAccepted());
//    }
    
    @Test
    public void loginIncorrectly() throws Exception {
    	userDTO1.setUsername("asgasg");
    	mockMvc.perform(post("/users/login")
	 			.contentType(contentType)
	 			.content(json(userDTO1)))
                .andExpect(status().isNotFound());
    }
    
    @Test
    public void register() throws Exception {
    	userDTO1.setUsername("ilanchoNew");
    	mockMvc.perform(post("/users/register")
	 			.contentType(contentType)
	 			.content(json(userDTO1)))
                .andExpect(status().isCreated());
    }
    
    @Test
    public void registerWithExistentUsername() throws Exception {
    	mockMvc.perform(post("/users/register")
	 			.contentType(contentType)
	 			.content(json(userDTO1)))
                .andExpect(status().isConflict());
    }
    
    @Test
    public void registerWithWrongUsernameData() throws Exception {
    	userDTO1.setUsername("ila");
    	mockMvc.perform(post("/users/register")
	 			.contentType(contentType)
	 			.content(json(userDTO1)))
                .andExpect(status().isBadRequest());
    }
    
    @Test
    public void registerWithWrongPasswordData() throws Exception {
    	userDTO1.setPassword("pass");
    	mockMvc.perform(post("/users/register")
	 			.contentType(contentType)
	 			.content(json(userDTO1)))
                .andExpect(status().isBadRequest());
    }
    
    @Test
    public void readAllUsers() throws Exception {
        mockMvc.perform(get("/users"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(contentType))
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[0].username", is(this.userDTO1.getUsername())))
  				.andExpect(jsonPath("$[1].username", is(this.userDTO2.getUsername())))
  				.andExpect(jsonPath("$[2].username", is(this.userDTO3.getUsername())));
    }
    
    @Test
	public void readAllSortedBy() throws Exception {
		mockMvc.perform(get("/users?sort=username"))
		.andExpect(content().contentType(contentType))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(3)))
        .andExpect(jsonPath("$[0].username", is(this.user3.getUsername())))
        .andExpect(jsonPath("$[1].username", is(this.user1.getUsername())))
        .andExpect(jsonPath("$[2].username", is(this.user2.getUsername())));
    }
	
	@Test
	public void readAllPaginated() throws Exception {
		mockMvc.perform(get("/users?page=0&size=2"))
		.andExpect(content().contentType(contentType))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(2)))
        .andExpect(jsonPath("$[0].username", is(this.user1.getUsername())))
        .andExpect(jsonPath("$[1].username", is(this.user2.getUsername())));
		mockMvc.perform(get("/users?page=1&size=2"))
		.andExpect(content().contentType(contentType))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(1)))
        .andExpect(jsonPath("$[0].username", is(this.user3.getUsername())));
	}
	
	@Test
	public void readAllPaginatedAndSorted() throws Exception {
		//u1: ilancho u2: juan u3: ben u4: zack
		Users userPersisted4= usersRepository.save(new Users("zack", "pass4", true));
		mockMvc.perform(get("/users?page=0&size=2&sort=-username"))
		.andExpect(content().contentType(contentType))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(2)))
		.andExpect(jsonPath("$[0].username", is(userPersisted4.getUsername())))
        .andExpect(jsonPath("$[1].username", is(this.user2.getUsername())));
		mockMvc.perform(get("/users?page=1&size=2&sort=-username"))
		.andExpect(content().contentType(contentType))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$", hasSize(2)))
        .andExpect(jsonPath("$[0].username", is(this.user1.getUsername())))
        .andExpect(jsonPath("$[1].username", is(this.user3.getUsername())));
	}
    
    protected String json(Object o) throws IOException {
        MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
        this.mappingJackson2HttpMessageConverter.write(
                o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
        return mockHttpOutputMessage.getBodyAsString();
    }
    
}
